﻿using System;
using System.Collections;
using System.Collections.Generic;

// This file contains reference data structures from OSTA UDF 1.02, and ECMA-167 (ISO-13346)
// Original names have been retained wherever possible, but extensive additions and changes 
// have been made for programmer convenience when dealing with C# conventions and .NET types.
// All classes are annotated where they are defined, but OSTA has a habit of defining structs 
// in several places, and ECMA and ISO duplicate many of them, so these annotations should
// not be considered exhaustive.

namespace UdfFactory
{
    /// <summary>Defined in OSTA UDF 1.02 2.3.11, ECMA-167 4/14.5</summary>
    public class AllocationExtentDescriptor
    {
        public AllocationExtentDescriptor(tag DescriptorTag, uint PreviousAllocationExtentLocation, uint LengthOfAllocationDescriptors)
        {
            this.DescriptorTag = DescriptorTag;
            this.PreviousAllocationExtentLocation = PreviousAllocationExtentLocation;
            this.LengthOfAllocationDescriptors = LengthOfAllocationDescriptors;
        }

        public tag DescriptorTag;
        public uint PreviousAllocationExtentLocation;
        public uint LengthOfAllocationDescriptors;

        public int Length { get { return 24; } }
        public static implicit operator byte[](AllocationExtentDescriptor a)
        {
            byte[] output = new byte[24];
            Array.Copy(a.DescriptorTag, 0, output, 0, 16);
            Array.Copy(ByteFactory.Convert(a.PreviousAllocationExtentLocation), 0, output, 16, 4);
            Array.Copy(ByteFactory.Convert(a.LengthOfAllocationDescriptors), 0, output, 20, 4);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 3.3.4.2, ECMA-167 4/14.10.4</summary>
    public class AlternatePermissionsExtendedAttribute
    {
        public AlternatePermissionsExtendedAttribute(uint AttributeType, byte AttributeSubtype,
            uint AttributeLength, ushort OwnerIdentification, ushort GroupIdentification, ushort Permission)
        {
            this.AttributeType = AttributeType;
            this.AttributeSubtype = AttributeSubtype;
            this.AttributeLength = AttributeLength;
            this.OwnerIdentification = OwnerIdentification;
            this.GroupIdentification = GroupIdentification;
            this.Permission = Permission;
        }

        public uint AttributeType;
        public byte AttributeSubtype;
        private readonly byte[] Reserved = new byte[3];
        public uint AttributeLength;
        public ushort OwnerIdentification;
        public ushort GroupIdentification;
        public ushort Permission;

        public int Length { get { return 18; } }
        public static implicit operator byte[](AlternatePermissionsExtendedAttribute a)
        {
            byte[] output = new byte[18];
            Array.Copy(ByteFactory.Convert(a.AttributeType), 0, output, 0, 4);
            output[4] = a.AttributeSubtype;
            Array.Copy(a.Reserved, 0, output, 5, 3);
            Array.Copy(ByteFactory.Convert(a.AttributeLength), 0, output, 8, 4);
            Array.Copy(ByteFactory.Convert(a.OwnerIdentification), 0, output, 12, 2);
            Array.Copy(ByteFactory.Convert(a.GroupIdentification), 0, output, 14, 2);
            Array.Copy(ByteFactory.Convert(a.Permission), 0, output, 16, 2);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.2.3, ECMA-167 3/10.2</summary>
    public class AnchorVolumeDescriptorPointer
    {
        public AnchorVolumeDescriptorPointer(tag DescriptorTag, extent_ad MainVolumeDescriptorSequenceExtent,
            extent_ad ReserveVolumeDescriptorSequenceExtent)
        {
            this.DescriptorTag = DescriptorTag;
            this.MainVolumeDescriptorSequenceExtent = MainVolumeDescriptorSequenceExtent;
            this.ReserveVolumeDescriptorSequenceExtent = ReserveVolumeDescriptorSequenceExtent;
        }

        public tag DescriptorTag;
        public extent_ad MainVolumeDescriptorSequenceExtent;
        public extent_ad ReserveVolumeDescriptorSequenceExtent;
        private readonly byte[] Reserved = new byte[480]; // <summary>Defined in OSTA UDF 1.02 2.2.3

        public int Length { get { return 512; } }
        public static implicit operator byte[](AnchorVolumeDescriptorPointer a)
        {
            byte[] output = new byte[512];
            Array.Copy(a.DescriptorTag, 0, output, 0, 16);
            Array.Copy(a.MainVolumeDescriptorSequenceExtent, 0, output, 16, 8);
            Array.Copy(a.ReserveVolumeDescriptorSequenceExtent, 0, output, 24, 8);
            Array.Copy(a.Reserved, 0, output, 32, 480);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 3.3.4.6, ECMA-167 4/14.10.9</summary>
    public class ApplicationUseExtendedAttribute
    {
        public ApplicationUseExtendedAttribute(byte AttributeSubtype, uint AttributeLength, EntityID ApplicationIdentifier, byte[] ApplicationUse)
        {
            this.AttributeSubtype = AttributeSubtype;
            this.AttributeLength = AttributeLength;
            this.ApplicationIdentifier = ApplicationIdentifier;
            this.ApplicationUse = ApplicationUse;
        }

        public readonly uint AttributeType = 65536;
        public byte AttributeSubtype;
        private readonly byte[] Reserved = new byte[3];
        public uint AttributeLength;
        public uint ApplicationUseLength { get { return (uint)ApplicationUse.Length; } }
        public EntityID ApplicationIdentifier;
        public byte[] ApplicationUse;

        public int Length { get { return 48 + (int)ApplicationUseLength; } }
        public static implicit operator byte[](ApplicationUseExtendedAttribute i)
        {
            byte[] output = new byte[i.Length];
            Array.Copy(ByteFactory.Convert(i.AttributeType), 0, output, 0, 4);
            output[4] = i.AttributeSubtype;
            Array.Copy(i.Reserved, 0, output, 5, 3);
            Array.Copy(ByteFactory.Convert(i.AttributeLength), 0, output, 8, 4);
            Array.Copy(ByteFactory.Convert(i.ApplicationUseLength), 0, output, 12, 4);
            Array.Copy(i.ApplicationIdentifier, 0, output, 16, 32);
            Array.Copy(i.ApplicationUse, 0, output, 48, (int)i.ApplicationUseLength);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.1.2</summary>
    public class charspec 
    {
        public charspec(byte CharacterSetType, byte[] CharacterSetInfo)
        {
            this.CharacterSetType = CharacterSetType;
            this.CharacterSetInfo = CharacterSetInfo;
        }

        public byte CharacterSetType;
        private byte[] _CharacterSetInfo;
        public byte[] CharacterSetInfo
        {
            get { return _CharacterSetInfo; }
            set { if (value.Length != 63) throw new ArgumentException(); else _CharacterSetInfo = value; }
        }

        public int Length { get { return 64; } }
        public static implicit operator byte[](charspec c)
        {
            byte[] output = new byte[64];
            output[0] = c.CharacterSetType;
            Array.Copy(c.CharacterSetInfo, 0, output, 1, 63);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 3.3.4.4, ECMA-167 4/14.10.7</summary>
    public class DeviceSpecificationExtendedAttribute
    {
        public DeviceSpecificationExtendedAttribute
            (uint AttributeType, byte AttributeSubtype, uint AttributeLength, uint MajorDeviceIdentification,
            uint MinorDeviceIdentification, byte[] ImplementationUse)
        {
            this.AttributeType = AttributeType;
            this.AttributeSubtype = AttributeSubtype;
            this.AttributeLength = AttributeLength;
            this.MajorDeviceIdentification = MajorDeviceIdentification;
            this.MinorDeviceIdentification = MinorDeviceIdentification;
            this.ImplementationUse = ImplementationUse;
        }

        public uint AttributeType;
        public byte AttributeSubtype;
        private readonly byte[] Reserved = new byte[3];
        public uint AttributeLength;
        public uint ImplementationUseLength { get { return (uint)ImplementationUse.Length; } }
        public uint MajorDeviceIdentification;
        public uint MinorDeviceIdentification;
        public byte[] ImplementationUse;

        public int Length { get { return 24 + (int)ImplementationUseLength; } }
        public static implicit operator byte[](DeviceSpecificationExtendedAttribute d)
        {
            byte[] output = new byte[d.Length];
            Array.Copy(ByteFactory.Convert(d.AttributeType), 0, output, 0, 4);
            output[4] = d.AttributeSubtype;
            Array.Copy(d.Reserved, 0, output, 5, 3);
            Array.Copy(ByteFactory.Convert(d.AttributeLength), 0, output, 8, 4);
            Array.Copy(ByteFactory.Convert(d.ImplementationUseLength), 0, output, 12, 4);
            Array.Copy(ByteFactory.Convert(d.MajorDeviceIdentification), 0, output, 16, 4);
            Array.Copy(ByteFactory.Convert(d.MinorDeviceIdentification), 0, output, 20, 4);
            Array.Copy(d.ImplementationUse, 0, output, 24, (int)d.ImplementationUseLength);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.1.5, ECMA-167 1/7.4</summary>
    public class EntityID 
    {
        public EntityID(byte Flags, byte[] Identifier, byte[] IdentifierSuffix)
        {
            this.Flags = Flags;
            this.Identifier = Identifier;
            this.IdentifierSuffix = IdentifierSuffix;
        }

        public byte Flags;
        private byte[] _Identifier;
        public byte[] Identifier
        {
            get { return _Identifier; }
            set { if (value.Length != 23) throw new ArgumentException(); else _Identifier = value; }
        }
        private byte[] _IdentifierSuffix;
        public byte[] IdentifierSuffix
        {
            get { return _IdentifierSuffix; }
            set { if (value.Length != 8) throw new ArgumentException(); else _IdentifierSuffix = value; }
        }

        public int Length { get { return 32; } }
        public static implicit operator byte[](EntityID e)
        {
            byte[] output = new byte[32];
            output[0] = e.Flags;
            Array.Copy(e.Identifier, 0, output, 1, 23);
            Array.Copy(e.IdentifierSuffix, 0, output, 24, 8);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 3.3.4.1, ECMA-167 4/14.10.1</summary>
    public class ExtendedAttributeHeaderDescriptor
    {
        public ExtendedAttributeHeaderDescriptor(tag DescriptorTag, uint ImplementationAttributesLocation, uint ApplicationAttributesLocation)
        {
            this.DescriptorTag = DescriptorTag;
            this.ImplementationAttributesLocation = ImplementationAttributesLocation;
            this.ApplicationAttributesLocation = ApplicationAttributesLocation;
        }

        public tag DescriptorTag;
        public uint ImplementationAttributesLocation;
        public uint ApplicationAttributesLocation;

        public int Length { get { return 24; } }
        public static implicit operator byte[](ExtendedAttributeHeaderDescriptor e)
        {
            byte[] output = new byte[24];
            Array.Copy(e.DescriptorTag, 0, output, 0, 16);
            Array.Copy(ByteFactory.Convert(e.ImplementationAttributesLocation), 0, output, 16, 4);
            Array.Copy(ByteFactory.Convert(e.ApplicationAttributesLocation), 0, output, 20, 4);
            return output;
        }
    }
    /// <summary>Defined in ECMA-167 3/7.1</summary>
    public class extent_ad
    {
        public extent_ad(uint ExtentLength, uint ExtentLocation)
        {
            this.ExtentLength = ExtentLength;
            this.ExtentLocation = ExtentLocation;
        }

        public uint ExtentLength;
        public uint ExtentLocation;

        public int Length { get { return 8; } }
        public static implicit operator byte[](extent_ad e)
        {
            byte[] output = new byte[8];
            Array.Copy(ByteFactory.Convert(e.ExtentLength), 0, output, 0, 4);
            Array.Copy(ByteFactory.Convert(e.ExtentLocation), 0, output, 4, 4);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.3.10.1, ECMA-167 4/14.14.3</summary>
    public class ext_ad
    {
        public ext_ad(uint ExtentLength, uint RecordedLength, uint InformationLength, lb_addr ExtentLocation, byte[] ImplementationUse)
        {
            this.ExtentLength = ExtentLength;
            this.RecordedLength = RecordedLength;
            this.InformationLength = InformationLength;
            this.ExtentLocation = ExtentLocation;
            this.ImplementationUse = ImplementationUse;
        }

        public uint ExtentLength;
        public uint RecordedLength;
        public uint InformationLength;
        public lb_addr ExtentLocation;
        private byte[] _ImplementationUse;
        public byte[] ImplementationUse
        {
            get { return _ImplementationUse; }
            set { if (value.Length != 2) throw new ArgumentException(); else _ImplementationUse = value; }
        }

        public int Length { get { return 20; } }
        public static implicit operator byte[](ext_ad e)
        {
            byte[] output = new byte[20];
            Array.Copy(ByteFactory.Convert(e.ExtentLength), 0, output, 0, 4);
            Array.Copy(ByteFactory.Convert(e.RecordedLength), 0, output, 4, 4);
            Array.Copy(ByteFactory.Convert(e.InformationLength), 0, output, 8, 4);
            Array.Copy(e.ExtentLocation, 0, output, 12, 6);
            Array.Copy(e.ImplementationUse, 0, output, 18, 2);
            return output;
        }
        public static implicit operator ext_ad(byte[] b)
        {
            Array.Reverse(b, 0, 4);
            Array.Reverse(b, 4, 4);
            Array.Reverse(b, 8, 4);
            byte[] lb_addr = new byte[6];
            Array.Copy(b, 12, lb_addr, 0, 6);
            byte[] ImplementationUse = new byte[2];
            Array.Copy(b, 18, ImplementationUse, 0, 2);
            return new ext_ad(BitConverter.ToUInt32(b, 0), BitConverter.ToUInt32(b, 4), BitConverter.ToUInt32(b, 8), lb_addr, ImplementationUse);
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.3.6, 3.3.3, ECMA-167 4/14.9</summary>
    public class FileEntry
    {
        // I don't like the way allocation descriptors are handled by ISO. That being said, I've built in some basic safeguards against
        // inadvertent corruption of AllocationDescriptors here. I'd prefer if it didn't have to be a byte[], but there may not be a
        // better way, since there are three AD types, all optional.

        public FileEntry(tag DescriptorTag, icbtag ICBTag, uint Uid, uint Gid, uint Permissions, ushort FileLinkCount,
            byte RecordFormat, byte RecordDisplayAttributes, uint RecordLength, ulong InformationLength, ulong LogicalBlocksRecorded,
            timestamp AccessTime, timestamp ModificationTime, timestamp AttributeTime, uint Checkpoint, long_ad ExtendedAttributeICB,
            EntityID ImplementationIdentifier, ulong UniqueID, byte[] ExtendedAttributes, byte[] AllocationDescriptors)
        {
            this.DescriptorTag = DescriptorTag;
            this.ICBTag = ICBTag;
            this.Uid = Uid;
            this.Gid = Gid;
            this.Permissions = Permissions;
            this.FileLinkCount = FileLinkCount;
            this.RecordFormat = RecordFormat;
            this.RecordDisplayAttributes = RecordDisplayAttributes;
            this.RecordLength = RecordLength;
            this.InformationLength = InformationLength;
            this.LogicalBlocksRecorded = LogicalBlocksRecorded;
            this.AccessTime = AccessTime;
            this.ModificationTime = ModificationTime;
            this.AttributeTime = AttributeTime;
            this.Checkpoint = Checkpoint;
            this.ExtendedAttributeICB = ExtendedAttributeICB;
            this.ImplementationIdentifier = ImplementationIdentifier;
            this.UniqueID = UniqueID;
            this.ExtendedAttributes = ExtendedAttributes;
            this.AllocationDescriptors = AllocationDescriptors;
        }

        public tag DescriptorTag;
        private icbtag _ICBTag;
        public icbtag ICBTag
        {
            get { return _ICBTag; }
            set
            {
                _ICBTag = value;
                if (value.Flags.ADType != _ICBTag.Flags.ADType)
                    AllocationDescriptors = null;
            }
        }
        public uint Uid;
        public uint Gid;
        public uint Permissions;
        public ushort FileLinkCount;
        public byte RecordFormat;
        public byte RecordDisplayAttributes;
        public uint RecordLength;
        public ulong InformationLength;
        public ulong LogicalBlocksRecorded;
        public timestamp AccessTime;
        public timestamp ModificationTime;
        public timestamp AttributeTime;
        public uint Checkpoint;
        public long_ad ExtendedAttributeICB;
        public EntityID ImplementationIdentifier;
        public ulong UniqueID;
        public uint LengthofExtendedAttributes { get { return (uint)ExtendedAttributes.Length; } }
        public uint LengthofAllocationDescriptors { get { return (uint)AllocationDescriptors.Length; } }
        public byte[] ExtendedAttributes;
        private byte[] _AllocationDescriptors;
        public byte[] AllocationDescriptors
        {
            get { return _AllocationDescriptors; }
            set
            {
                byte entryLength = 1;
                switch(ICBTag.Flags.ADType)
                {
                    case 0x00: // short_ad
                        entryLength = 8;
                        break;
                    case 0x01: // long_ad
                        entryLength = 16;
                        break;
                    case 0x02: // ext_ad
                        entryLength = 20;
                        break;
                }
                if (AllocationDescriptors.Length % entryLength != 0)
                    throw new ArgumentException("The length of AllocationDescriptors must be a multiple of the descriptor size ( " + entryLength.ToString() + " ) specified by the ICB tag!");
                _AllocationDescriptors = value;
            }
        }

        public int Length { get { return (int)(176 + LengthofExtendedAttributes + LengthofAllocationDescriptors); } }
        public static implicit operator byte[](FileEntry f)
        {
            byte[] output = new byte[f.Length];
            Array.Copy(f.DescriptorTag, 0, output, 0, 16);
            Array.Copy(f.ICBTag, 0, output, 0, 20);
            Array.Copy(ByteFactory.Convert(f.Uid), 0, output, 36, 4);
            Array.Copy(ByteFactory.Convert(f.Gid), 0, output, 40, 4);
            Array.Copy(ByteFactory.Convert(f.Permissions), 0, output, 44, 4);
            Array.Copy(ByteFactory.Convert(f.FileLinkCount), 0, output, 48, 2);
            output[50] = f.RecordFormat;
            output[51] = f.RecordDisplayAttributes;
            Array.Copy(ByteFactory.Convert(f.RecordLength), 0, output, 52, 4);
            Array.Copy(ByteFactory.Convert(f.InformationLength), 0, output, 56, 8);
            Array.Copy(ByteFactory.Convert(f.LogicalBlocksRecorded), 0, output, 64, 8);
            Array.Copy(f.AccessTime, 0, output, 72, 12);
            Array.Copy(f.ModificationTime, 0, output, 84, 12);
            Array.Copy(f.AttributeTime, 0, output, 96, 12);
            Array.Copy(ByteFactory.Convert(f.Checkpoint), 0, output, 108, 4);
            Array.Copy(f.ExtendedAttributeICB, 0, output, 112, 16);
            Array.Copy(f.ImplementationIdentifier, 0, output, 128, 32);
            Array.Copy(ByteFactory.Convert(f.UniqueID), 0, output, 160, 8);
            Array.Copy(ByteFactory.Convert(f.LengthofExtendedAttributes), 0, output, 168, 4);
            Array.Copy(ByteFactory.Convert(f.LengthofAllocationDescriptors), 0, output, 172, 4);
            Array.Copy(f.ExtendedAttributes, 0, output, 176, (int)f.LengthofExtendedAttributes);
            Array.Copy(f.AllocationDescriptors, 0, output, (int)(176 + f.LengthofExtendedAttributes), (int)f.LengthofAllocationDescriptors);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.3.5.4, ECMA-167 4/14.6.8, ISO 13346 4/14.6.8</summary>
    public class FileFlags
    {
        private BitArray value = new BitArray(16,false);
        // No explicit constructor. Not a big deal, since typically this type will need to be
        // instantiated and then have several flags set before use in any case.

        public byte ADType // TODO: MSB? LSB? Spec is unclear. Verify.
        {
            get
            {
                byte output = 0x00;
                if (value[0])
                    output |= 0x01;
                if (value[1])
                    output |= 0x02;
                if (value[2])
                    output |= 0x04;
                return output;
            }
            set
            {
                if ((value & 0x01) == 0x01)
                    this.value[0] = true;
                if ((value & 0x02) == 0x02)
                    this.value[1] = true;
                if ((value & 0x04) == 0x04)
                    this.value[2] = true;
            }
        }
        public bool DirectorySorted
        {
            get { return value[3]; }
            set { this.value[3] = value; }
        }
        public bool NonRelocatable
        {
            get { return value[4]; }
            set { this.value[4] = value; }
        }
        public bool Archive
        {
            get { return value[5]; }
            set { this.value[5] = value; }
        }
        public bool SetUid
        {
            get { return value[6]; }
            set { this.value[6] = value; }
        }
        public bool SetGid
        {
            get { return value[7]; }
            set { this.value[7] = value; }
        }
        public bool Sticky
        {
            get { return value[8]; }
            set { this.value[8] = value; }
        }
        public bool Contiguous
        {
            get { return value[9]; }
            set { this.value[9] = value; }
        }
        public bool System
        {
            get { return value[10]; }
            set { this.value[10] = value; }
        }
        public bool Transformed
        {
            get { return value[11]; }
            set { this.value[11] = value; }
        }
        public bool MultiVersions
        {
            get { return value[12]; }
            set { this.value[12] = value; }
        }
        public bool Stream
        {
            get { return value[13]; }
            set { this.value[13] = value; }
        }

        public static implicit operator FileFlags(ushort u)
        {
            FileFlags output = new FileFlags();
            for (int i = 15; i > -1; i--)
                if (u / (2 ^ i) >= 1)
                {
                    output.value[i] = true;
                    u -= (ushort)(2 ^ i);
                }
            return output;
        }
        public static implicit operator ushort(FileFlags f)
        {
            ushort output = 0;
            for (int i = 0; i < 16; i++)
                if (f.value[i])
                    output += (ushort)(2 ^ i);
            return output;
        }


    }
    /// <summary>Defined in OSTA UDF 1.02 2.3.4, 3.3.1, ECMA-167 4/14.4</summary>
    public class FileIdentifierDescriptor 
    {
        public FileIdentifierDescriptor(tag DescriptorTag, ushort FileVersionNumber, byte FileCharacteristics,
            long_ad ICB, EntityID ImplementationUse, string FileIdentifier)
        {
            this.DescriptorTag = DescriptorTag;
            this.FileVersionNumber = FileVersionNumber;
            this.FileCharacteristics = FileCharacteristics;
            this.ICB = ICB;
            this.ImplementationUse = ImplementationUse;
            this.FileIdentifier = FileIdentifier;
        }

        public tag DescriptorTag;
        public ushort FileVersionNumber;
        public byte FileCharacteristics;
        public byte LengthofFileIdentifier { get { return (byte)FileIdentifier.Length; } }
        public long_ad ICB;
        public ushort LengthofImplementationUse { get { return (ushort)ImplementationUse.Length; } }
        private byte[] _ImplementationUse;
        public byte[] ImplementationUse
        {
            get { return _ImplementationUse; }
            set { if (value.Length % 4 != 0) throw new ArgumentException(); else _ImplementationUse = value; }
        }
        private string _FileIdentifier;
        public string FileIdentifier
        {
            get { return _FileIdentifier; }
            set { if (value.Length > 255) throw new ArgumentException(); else _FileIdentifier = value; }
        }

        public int Length { get { return 38 + (int)LengthofImplementationUse + (int)LengthofFileIdentifier; } }
        public static implicit operator byte[](FileIdentifierDescriptor f)
        {
            byte[] output = new byte[f.Length];
            Array.Copy(f.DescriptorTag, 0, output, 0, 16);
            Array.Copy(ByteFactory.Convert(f.FileVersionNumber), 0, output, 16, 2);
            output[18] = f.FileCharacteristics;
            output[19] = f.LengthofFileIdentifier;
            Array.Copy(f.ICB, 0, output, 20, 16);
            Array.Copy(ByteFactory.Convert(f.LengthofImplementationUse), 0, output, 36, 2);
            Array.Copy(f.ImplementationUse, 0, output, 38, f.LengthofImplementationUse);
            Array.Copy(ByteFactory.Convert(f.FileIdentifier), 0, output, 38 + f.LengthofImplementationUse, f.LengthofFileIdentifier);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.3.2, ECMA-167 4/14.1</summary>
    public class FileSetDescriptor
    {
        public FileSetDescriptor(tag DescriptorTag, timestamp RecordingDateAndTime, ushort InterchangeLevel,
            ushort MaximumInterchangeLevel, uint CharacterSetList, uint MaximumCharacterSetList, uint FileSetNumber,
            uint FileSetDescriptorNumber, charspec LogicalVolumeIdentifierCharacterSet, byte[] LogicalVolumeIdentifier,
            charspec FileSetCharacterSet, byte[] FileSetIdentifier, byte[] CopyrightFileIdentifier, byte[] AbstractFileIdentifier,
            long_ad RootDirectoryICB, EntityID DomainIdentifier, long_ad NextExtent)
        {
            this.DescriptorTag = DescriptorTag;
            this.RecordingDateAndTime = RecordingDateAndTime;
            this.InterchangeLevel = InterchangeLevel;
            this.MaximumInterchangeLevel = MaximumInterchangeLevel;
            this.CharacterSetList = CharacterSetList;
            this.MaximumCharacterSetList = MaximumCharacterSetList;
            this.FileSetNumber = FileSetNumber;
            this.FileSetDescriptorNumber = FileSetDescriptorNumber;
            this.LogicalVolumeIdentifierCharacterSet = LogicalVolumeIdentifierCharacterSet;
            this.LogicalVolumeIdentifier = LogicalVolumeIdentifier;
            this.FileSetCharacterSet = FileSetCharacterSet;
            this.FileSetIdentifier = FileSetIdentifier;
            this.CopyrightFileIdentifier = CopyrightFileIdentifier;
            this.AbstractFileIdentifier = AbstractFileIdentifier;
            this.RootDirectoryICB = RootDirectoryICB;
            this.DomainIdentifier = DomainIdentifier;
            this.NextExtent = NextExtent;
        }

        public tag DescriptorTag;
        public timestamp RecordingDateAndTime;
        public ushort InterchangeLevel;
        public ushort MaximumInterchangeLevel;
        public uint CharacterSetList;
        public uint MaximumCharacterSetList;
        public uint FileSetNumber;
        public uint FileSetDescriptorNumber;
        public charspec LogicalVolumeIdentifierCharacterSet;
        private byte[] _LogicalVolumeIdentifier;
        public byte[] LogicalVolumeIdentifier
        {
            get { return _LogicalVolumeIdentifier; }
            set { if (value.Length != 128) throw new ArgumentException(); else _LogicalVolumeIdentifier = value; }
        }
        public charspec FileSetCharacterSet;
        private byte[] _FileSetIdentifier;
        public byte[] FileSetIdentifier
        {
            get { return _FileSetIdentifier; }
            set { if (value.Length != 32) throw new ArgumentException(); else _FileSetIdentifier = value; }
        }
        private byte[] _CopyrightFileIdentifier;
        public byte[] CopyrightFileIdentifier
        {
            get { return _CopyrightFileIdentifier; }
            set { if (value.Length != 32) throw new ArgumentException(); else _CopyrightFileIdentifier = value; }
        }
        private byte[] _AbstractFileIdentifier;
        public byte[] AbstractFileIdentifier
        {
            get { return _AbstractFileIdentifier; }
            set { if (value.Length != 32) throw new ArgumentException(); else _AbstractFileIdentifier = value; }
        }
        public long_ad RootDirectoryICB;
        public EntityID DomainIdentifier;
        public long_ad NextExtent;
        private readonly byte[] Reserved = new byte[48];

        public int Length { get { return 512; } }
        public static implicit operator byte[](FileSetDescriptor f)
        {
            byte[] output = new byte[512];
            Array.Copy(f.DescriptorTag, 0, output, 0, 16);
            Array.Copy(f.RecordingDateAndTime, 0, output, 16, 12);
            Array.Copy(ByteFactory.Convert(f.InterchangeLevel), 0, output, 28, 2);
            Array.Copy(ByteFactory.Convert(f.MaximumInterchangeLevel), 0, output, 30, 2);
            Array.Copy(ByteFactory.Convert(f.CharacterSetList), 0, output, 32, 4);
            Array.Copy(ByteFactory.Convert(f.MaximumCharacterSetList), 0, output, 36, 4);
            Array.Copy(ByteFactory.Convert(f.FileSetNumber), 0, output, 40, 4);
            Array.Copy(ByteFactory.Convert(f.FileSetDescriptorNumber), 0, output, 44, 4);
            Array.Copy(f.LogicalVolumeIdentifierCharacterSet, 0, output, 48, 64);
            Array.Copy(f.LogicalVolumeIdentifier, 0, output, 112, 128);
            Array.Copy(f.FileSetCharacterSet, 0, output, 240, 64);
            Array.Copy(f.FileSetIdentifier, 0, output, 304, 32);
            Array.Copy(f.CopyrightFileIdentifier, 0, output, 336, 32);
            Array.Copy(f.AbstractFileIdentifier, 0, output, 368, 32);
            Array.Copy(f.RootDirectoryICB, 0, output, 400, 16);
            Array.Copy(f.DomainIdentifier, 0, output, 416, 32);
            Array.Copy(f.NextExtent, 0, output, 448, 16);
            Array.Copy(f.Reserved, 0, output, 464, 48);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 3.3.4.3, ECMA-167 4/14.10.5</summary>
    public class FileTimesExtendedAttribute
    {
        public FileTimesExtendedAttribute(uint AttributeType, byte AttributeSubtype,
        uint AttributeLength, uint FileTimeExistence, byte[] FileTimes)
        {
            this.AttributeType = AttributeType;
            this.AttributeSubtype = AttributeSubtype;
            this.AttributeLength = AttributeLength;
            this.FileTimeExistence = FileTimeExistence;
            this.FileTimes = FileTimes;
        }

        public uint AttributeType;
        public byte AttributeSubtype;
        private readonly byte[] Reserved = new byte[3];
        public uint AttributeLength;
        public uint DataLength { get { return (uint)FileTimes.Length; } }
        public uint FileTimeExistence;
        public byte[] FileTimes;

        public int Length { get { return 20 + (int)DataLength; } }
        public static implicit operator byte[](FileTimesExtendedAttribute f)
        {
            byte[] output = new byte[21];
            Array.Copy(ByteFactory.Convert(f.AttributeType), 0, output, 0, 4);
            output[4] = f.AttributeSubtype;
            Array.Copy(f.Reserved, 0, output, 5, 3);
            Array.Copy(ByteFactory.Convert(f.AttributeLength), 0, output, 8, 4);
            Array.Copy(ByteFactory.Convert(f.DataLength), 0, output, 12, 4);
            Array.Copy(ByteFactory.Convert(f.FileTimeExistence), 0, output, 16, 4);
            Array.Copy(f.FileTimes, 0, output, 20, (int)f.DataLength);
            return output;
        }
    }
    /// <summary>Defined in ECMA-167 4/14.6.6, ISO 13346 4/14.6.6</summary>
    public class FileType
    {
        private byte value;
        public FileType(byte b)
        { value = b; }

        public static FileType NotSpecified()
        { return 0; }
        public static FileType UnallocatedSpaceEntry()
        { return 1; }
        public static FileType PartitionIntegrityEntry()
        { return 2; }
        public static FileType IndirectEntry()
        { return 3; }
        public static FileType Directory()
        { return 4; }
        public static FileType File()
        { return 5; }
        public static FileType BlockSpecial()
        { return 6; }
        public static FileType CharacterSpecial()
        { return 7; }
        public static FileType ExtendedAttributes()
        { return 8; }
        public static FileType FIFO()
        { return 9; }
        public static FileType C_ISSOCK()
        { return 10; }
        public static FileType TerminalEntry()
        { return 11; }
        public static FileType SymbolicLink()
        { return 12; }
        public static FileType StreamDirectory()
        { return 13; }

        public static implicit operator FileType(byte b)
        { return new FileType(b); }
        public static implicit operator byte(FileType f)
        { return f.value; }
    }
    /// <summary>Defined in OSTA UDF 1.02 3.3.2, ECMA-167 4/14.6</summary>
    public class icbtag
    {
        public icbtag(uint PriorRecordedNumberofDirectEntries, ushort StrategyType, byte[] StrategyParameter,
        ushort MaximumNumberofEntries, FileType FileType, lb_addr ParentICBLocation, FileFlags Flags)
        {
            this.PriorRecordedNumberofDirectEntries = PriorRecordedNumberofDirectEntries;
            this.StrategyType = StrategyType;
            this.StrategyParameter = StrategyParameter;
            this.MaximumNumberofEntries = MaximumNumberofEntries;
            this.FileType = FileType;
            this.ParentICBLocation = ParentICBLocation;
            this.Flags = Flags;
        }

        public uint PriorRecordedNumberofDirectEntries;
        public ushort StrategyType;
        private byte[] _StrategyParameter;
        public byte[] StrategyParameter
        {
            get { return _StrategyParameter; }
            set { if (value.Length != 2) throw new ArgumentException(); else _StrategyParameter = value; }
        }
        public ushort MaximumNumberofEntries;
        private readonly byte Reserved = 0x00;
        public FileType FileType;
        public lb_addr ParentICBLocation;
        public FileFlags Flags;

        public int Length { get { return 20; } }
        public static implicit operator byte[](icbtag i)
        {
            byte[] output = new byte[20];
            Array.Copy(ByteFactory.Convert(i.PriorRecordedNumberofDirectEntries), 0, output, 0, 4);
            Array.Copy(ByteFactory.Convert(i.StrategyType), 0, output, 4, 2);
            Array.Copy(i.StrategyParameter, 0, output, 6, 2);
            Array.Copy(ByteFactory.Convert(i.MaximumNumberofEntries), 0, output, 8, 2);
            output[10] = i.Reserved;
            output[11] = i.FileType;
            Array.Copy(i.ParentICBLocation, 0, output, 12, 6);
            Array.Copy(ByteFactory.Convert(i.Flags), 0, output, 18, 2);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 3.3.4.5, ECMA-167 4/14.10.8</summary>
    public class ImplementationUseExtendedAttribute
    {
        public ImplementationUseExtendedAttribute(uint AttributeType, byte AttributeSubtype,
        uint AttributeLength, EntityID ImplementationIdentifier, byte[] ImplementationUse)
        {
            this.AttributeType = AttributeType;
            this.AttributeSubtype = AttributeSubtype;
            this.AttributeLength = AttributeLength;
            this.ImplementationIdentifier = ImplementationIdentifier;
            this.ImplementationUse = ImplementationUse;
        }

        public uint AttributeType;
        public byte AttributeSubtype;
        private readonly byte[] Reserved = new byte[3];
        public uint AttributeLength;
        public uint ImplementationUseLength { get { return (uint)ImplementationUse.Length; } }
        public EntityID ImplementationIdentifier;
        public byte[] ImplementationUse;

        public int Length { get { return 48 + (int)ImplementationUseLength; } }
        public static implicit operator byte[](ImplementationUseExtendedAttribute i)
        {
            byte[] output = new byte[i.Length];
            Array.Copy(ByteFactory.Convert(i.AttributeType), 0, output, 0, 4);
            output[4] = i.AttributeSubtype;
            Array.Copy(i.Reserved, 0, output, 5, 3);
            Array.Copy(ByteFactory.Convert(i.AttributeLength), 0, output, 8, 4);
            Array.Copy(ByteFactory.Convert(i.ImplementationUseLength), 0, output, 12, 4);
            Array.Copy(i.ImplementationIdentifier, 0, output, 16, 32);
            Array.Copy(i.ImplementationUse, 0, output, 48, (int)i.ImplementationUseLength);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.2.7</summary>
    public class ImpUseVolumeDescriptor
    {
        public ImpUseVolumeDescriptor(tag DescriptorTag, uint VolumeDescriptorSequenceNumber,
            EntityID ImplementationIdentifier, LVInformation ImplementationUse)
        {
            this.DescriptorTag = DescriptorTag;
            this.VolumeDescriptorSequenceNumber = VolumeDescriptorSequenceNumber;
            this.ImplementationIdentifier = ImplementationIdentifier;
            this.ImplementationUse = ImplementationUse;
        }

        public tag DescriptorTag;
        public uint VolumeDescriptorSequenceNumber;
        public EntityID ImplementationIdentifier;
        public LVInformation ImplementationUse;

        public int Length { get { return 512; } }
        public static implicit operator byte[](ImpUseVolumeDescriptor i)
        {
            byte[] output = new byte[512];
            Array.Copy(i.DescriptorTag, 0, output, 0, 16);
            Array.Copy(ByteFactory.Convert(i.VolumeDescriptorSequenceNumber), 0, output, 16, 4);
            Array.Copy(i.ImplementationIdentifier, 0, output, 20, 32);
            Array.Copy(i.ImplementationUse, 0, output, 52, 460);
            return output;
        }
    }
    /// <summary>Defined in ECMA-167 4/7.1</summary>
    public class lb_addr 
    {
        public lb_addr(uint LogicalBlockNumber, ushort PartitionReferenceNumber)
        {
            this.LogicalBlockNumber = LogicalBlockNumber;
            this.PartitionReferenceNumber = PartitionReferenceNumber;
        }

        public uint LogicalBlockNumber;
        public ushort PartitionReferenceNumber;

        public int Length { get { return 6; } }
        public static implicit operator byte[](lb_addr l)
        {
            byte[] output = new byte[6];
            Array.Copy(ByteFactory.Convert(l.LogicalBlockNumber), 0, output, 0, 4);
            Array.Copy(ByteFactory.Convert(l.PartitionReferenceNumber), 0, output, 4, 2);
            return output;
        }
        public static implicit operator lb_addr(byte[] b)
        {
            Array.Reverse(b, 0, 4);
            Array.Reverse(b, 4, 2);
            return new lb_addr(BitConverter.ToUInt32(b, 0), BitConverter.ToUInt16(b, 4));
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.2.4, ECMA-167 3/10.6</summary>
    public class LogicalVolumeDescriptor
    {
        public LogicalVolumeDescriptor(tag DescriptorTag, uint VolumeDescriptorSequenceNumber,
            charspec DescriptorCharacterSet, byte[] LogicalVolumeIdentifier, uint LogicalBlockSize,
            EntityID DomainIdentifier, byte[] LogicalVolumeContentsUse, EntityID ImplementationIdentifier,
            byte[] ImplementationUse, extent_ad IntegritySequenceExtent, List<Type1PartitionMap> PartitionMaps)
        {
            this.DescriptorTag = DescriptorTag;
            this.VolumeDescriptorSequenceNumber = VolumeDescriptorSequenceNumber;
            this.DescriptorCharacterSet = DescriptorCharacterSet;
            this.LogicalVolumeIdentifier = LogicalVolumeIdentifier;
            this.LogicalBlockSize = LogicalBlockSize;
            this.DomainIdentifier = DomainIdentifier;
            this.LogicalVolumeContentsUse = LogicalVolumeContentsUse;
            this.ImplementationIdentifier = ImplementationIdentifier;
            this.ImplementationUse = ImplementationUse;
            this.IntegritySequenceExtent = IntegritySequenceExtent;
            this.PartitionMaps = PartitionMaps;
        }

        public tag DescriptorTag;
        public uint VolumeDescriptorSequenceNumber;
        public charspec DescriptorCharacterSet;
        private byte[] _LogicalVolumeIdentifier;
        public byte[] LogicalVolumeIdentifier
        {
            get { return _LogicalVolumeIdentifier; }
            set { if (value.Length != 128)throw new ArgumentException(); else _LogicalVolumeIdentifier = value; }
        }
        public uint LogicalBlockSize;
        public EntityID DomainIdentifier;
        private byte[] _LogicalVolumeContentsUse;
        public byte[] LogicalVolumeContentsUse
        {
            get { return _LogicalVolumeContentsUse; }
            set { if (value.Length != 16)throw new ArgumentException(); else _LogicalVolumeContentsUse = value; }
        }
        public uint MapTableLength { get { return NumberofPartitionMaps * 8; } }
        public uint NumberofPartitionMaps { get { return (uint)PartitionMaps.Count; } }
        public EntityID ImplementationIdentifier;
        private byte[] _ImplementationUse;
        public byte[] ImplementationUse
        {
            get { return _ImplementationUse; }
            set { if (value.Length != 128)throw new ArgumentException(); else _ImplementationUse = value; }
        }
        public extent_ad IntegritySequenceExtent;
        public List<Type1PartitionMap> PartitionMaps;

        public int Length { get { return 440 + (int)MapTableLength; } }
        public static implicit operator byte[](LogicalVolumeDescriptor l)
        {
            byte[] output = new byte[l.Length];
            Array.Copy(l.DescriptorTag, 0, output, 0, 16);
            Array.Copy(ByteFactory.Convert(l.VolumeDescriptorSequenceNumber), 0, output, 16, 4);
            Array.Copy(l.DescriptorCharacterSet, 0, output, 20, 64);
            Array.Copy(l.LogicalVolumeIdentifier, 0, output, 84, 128);
            Array.Copy(ByteFactory.Convert(l.LogicalBlockSize), 0, output, 212, 4);
            Array.Copy(l.DomainIdentifier, 0, output, 216, 32);
            Array.Copy(l.LogicalVolumeContentsUse, 0, output, 248, 16);
            Array.Copy(ByteFactory.Convert(l.MapTableLength), 0, output, 264, 4);
            Array.Copy(ByteFactory.Convert(l.NumberofPartitionMaps), 0, output, 268, 4);
            Array.Copy(l.ImplementationIdentifier, 0, output, 272, 32);
            Array.Copy(l.ImplementationUse, 0, output, 304, 128);
            Array.Copy(l.IntegritySequenceExtent, 0, output, 432, 8);
            for (int i = 0; i < l.PartitionMaps.Count; i++)
                Array.Copy(l.PartitionMaps[i], 0, output, 440 + (i * 8), 8);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 3.2.1, ECMA-167 4/14.15</summary>
    public class LogicalVolumeHeaderDesc
    {
        public LogicalVolumeHeaderDesc(ulong UniqueID)
        {
            this.UniqueID = UniqueID;
        }

        public ulong UniqueID;
        private readonly byte[] reserved = new byte[24];

        public int Length { get { return 32; } }
        public static implicit operator byte[](LogicalVolumeHeaderDesc l)
        {
            byte[] output = new byte[32];
            Array.Copy(ByteFactory.Convert(l.UniqueID), 0, output, 0, 8);
            Array.Copy(l.reserved, 0, output, 8, 24);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.2.6, ECMA-167 3/10.10</summary>
    public class LogicalVolumeIntegrityDesc
    {
        // Note: NumberOfPartitions, when set, sets the size of FreeSpaceTable and SizeTable. Elsewhere, length values are
        // generated on demand by a get accessor, but in this case there are two arrays that MUST always be the same size.
        // I thought about using a List<Tuple<uint,uint>>, but that seems a bit overwrought here, and immutable values create
        // a minor problem of their own. Instead, setting or resetting NumberOfPartitions resizes the arrays for you, which
        // can then be accessed as usual, with the restriction that an exception is thrown if an attempt is made to change 
        // their size (which would corrupt the descriptor as a whole). Granted, this structure is not something that will be
        // fooled with too often, but UDF is complicated enough, so why not abstract away a bit of complexity here?

        public LogicalVolumeIntegrityDesc(tag DescriptorTag, timestamp RecordingDateAndTime,
            uint IntegrityType, extent_ad NextIntegrityExtent, byte[] LogicalVolumeContentsUse,
            uint v, uint[] FreeSpaceTable, uint[] SizeTable, byte[] ImplementationUse)
        {
            this.DescriptorTag = DescriptorTag;
            this.RecordingDateAndTime = RecordingDateAndTime;
            this.IntegrityType = IntegrityType;
            this.NextIntegrityExtent = NextIntegrityExtent;
            this.LogicalVolumeContentsUse = LogicalVolumeContentsUse;
            this.NumberOfPartitions = NumberOfPartitions;
            this.FreeSpaceTable = FreeSpaceTable;
            this.SizeTable = SizeTable;
            this.ImplementationUse = ImplementationUse;
        }

        public tag DescriptorTag;
        public timestamp RecordingDateAndTime;
        public uint IntegrityType;
        public extent_ad NextIntegrityExtent;
        private byte[] _LogicalVolumeContentsUse;
        public byte[] LogicalVolumeContentsUse
        {
            get { return _LogicalVolumeContentsUse; }
            set { if (value.Length != 32)throw new ArgumentException(); else _LogicalVolumeContentsUse = value; }
        }
        private uint _NumberOfPartitions;
        public uint NumberOfPartitions
        {
            get { return _NumberOfPartitions; }
            set
            {
                _NumberOfPartitions = value;
                uint[] FreeSpaceTable = new uint[value];
                Array.Copy(_FreeSpaceTable, 0, FreeSpaceTable, 0, (int)value);
                _FreeSpaceTable = FreeSpaceTable;
                uint[] SizeTable = new uint[value];
                Array.Copy(_SizeTable, 0, SizeTable, 0, (int)value);
                _SizeTable = SizeTable;
            }
        }
        public uint LengthOfImplementationUse { get { return (uint)ImplementationUse.Length; } }
        private uint[] _FreeSpaceTable = new uint[0];
        public uint[] FreeSpaceTable
        {
            get { return _FreeSpaceTable; }
            set { if (value.Length != NumberOfPartitions)throw new ArgumentException(); else _FreeSpaceTable = value; }
        }
        private uint[] _SizeTable = new uint[0];
        public uint[] SizeTable
        {
            get { return _SizeTable; }
            set { if (value.Length != NumberOfPartitions)throw new ArgumentException(); else _SizeTable = value; }
        }
        public byte[] ImplementationUse;

        public int Length { get { return 80 + ((int)NumberOfPartitions * 8) + (int)LengthOfImplementationUse; } }
        public static implicit operator byte[](LogicalVolumeIntegrityDesc l)
        {
            byte[] output = new byte[l.Length];
            Array.Copy(l.DescriptorTag, 0, output, 0, 16);
            Array.Copy(l.RecordingDateAndTime, 0, output, 16, 12);
            Array.Copy(ByteFactory.Convert(l.IntegrityType), 0, output, 28, 4);
            Array.Copy(l.NextIntegrityExtent, 0, output, 32, 8);
            Array.Copy(l.LogicalVolumeContentsUse, 0, output, 40, 32);
            Array.Copy(ByteFactory.Convert(l.NumberOfPartitions), 0, output, 72, 4);
            Array.Copy(ByteFactory.Convert(l.LengthOfImplementationUse), 0, output, 76, 4);
            Array.Copy(l.FreeSpaceTable, 0, output, 80, (int)l.NumberOfPartitions * 4);
            Array.Copy(l.SizeTable, 0, output, 80 + (int)l.NumberOfPartitions * 4, (int)l.NumberOfPartitions * 4);
            Array.Copy(l.ImplementationUse, 0, output, 80 + (int)l.NumberOfPartitions * 8, l.ImplementationUse.Length);
            return output;
        }
    }
    /// <summary>Defined in ECMA-167 4/14.14.2</summary>
    public class long_ad
    {
        public long_ad(uint ExtentLength, lb_addr ExtentLocation, byte[] ImplementationUse)
        {
            this.ExtentLength = ExtentLength;
            this.ExtentLocation = ExtentLocation;
            this.ImplementationUse = ImplementationUse;
        }

        public uint ExtentLength;
        public lb_addr ExtentLocation;
        private byte[] _ImplementationUse;
        public byte[] ImplementationUse
        {
            get { return _ImplementationUse; }
            set { if (value.Length != 6) throw new ArgumentException(); else _ImplementationUse = value; }
        }

        public int Length { get { return 16; } }
        public static implicit operator byte[](long_ad l)
        {
            byte[] output = new byte[16];
            Array.Copy(ByteFactory.Convert(l.ExtentLength), 0, output, 0, 4);
            Array.Copy(l.ExtentLocation, 0, output, 4, 6);
            Array.Copy(l.ImplementationUse, 0, output, 10, 6);
            return output;
        }
        public static implicit operator long_ad(byte[] b)
        {
            Array.Reverse(b, 0, 4);
            byte[] lb_addr = new byte[6];
            Array.Copy(b, 4, lb_addr, 0, 6);
            byte[] ImplementationUse = new byte[6];
            Array.Copy(b, 10, ImplementationUse, 0, 6);
            return new long_ad(BitConverter.ToUInt32(b, 0), lb_addr, ImplementationUse);
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.2.7.2</summary>
    public class LVInformation
    {
        public LVInformation(charspec LVICharset, byte[] LogicalVolumeIdentifier, byte[] LVInfo1,
            byte[] LVInfo2, byte[] LVInfo3, EntityID ImplementionID, byte[] ImplementationUse)
        {
            this.LVICharset = LVICharset;
            this.LogicalVolumeIdentifier = LogicalVolumeIdentifier;
            this.LVInfo1 = LVInfo1;
            this.LVInfo2 = LVInfo2;
            this.LVInfo3 = LVInfo3;
            this.ImplementionID = ImplementionID;
            this.ImplementationUse = ImplementationUse;
        }

        public charspec LVICharset;
        private byte[] _LogicalVolumeIdentifier;
        public byte[] LogicalVolumeIdentifier
        {
            get { return _LogicalVolumeIdentifier; }
            set { if (value.Length != 128) throw new ArgumentException(); else _LogicalVolumeIdentifier = value; }
        }
        private byte[] _LVInfo1;
        public byte[] LVInfo1
        {
            get { return _LVInfo1; }
            set { if (value.Length != 36) throw new ArgumentException(); else _LVInfo1 = value; }
        }
        private byte[] _LVInfo2;
        public byte[] LVInfo2
        {
            get { return _LVInfo2; }
            set { if (value.Length != 36) throw new ArgumentException(); else _LVInfo2 = value; }
        }
        private byte[] _LVInfo3;
        public byte[] LVInfo3
        {
            get { return _LVInfo3; }
            set { if (value.Length != 36) throw new ArgumentException(); else _LVInfo3 = value; }
        }
        public EntityID ImplementionID;
        private byte[] _ImplementationUse;
        public byte[] ImplementationUse
        {
            get { return _ImplementationUse; }
            set { if (value.Length != 128) throw new ArgumentException(); else _ImplementationUse = value; }
        }

        public int Length { get { return 460; } }
        public static implicit operator byte[](LVInformation l)
        {
            byte[] output = new byte[460];
            Array.Copy(l.LVICharset, 0, output, 0, 64);
            Array.Copy(l.LogicalVolumeIdentifier, 0, output, 64, 128);
            Array.Copy(l.LVInfo1, 0, output, 192, 36);
            Array.Copy(l.LVInfo2, 0, output, 228, 36);
            Array.Copy(l.LVInfo3, 0, output, 264, 36);
            Array.Copy(l.ImplementionID, 0, output, 300, 32);
            Array.Copy(l.ImplementationUse, 0, output, 336, 128);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.3.3, ECMA-167 4/14.3</summary>
    public class PartitionHeaderDescriptor
    {
        public PartitionHeaderDescriptor(short_ad UnallocatedSpaceTable, short_ad UnallocatedSpaceBitmap,
            short_ad PartitionIntegrityTable, short_ad FreedSpaceTable, short_ad FreedSpaceBitmap)
        {
            this.UnallocatedSpaceTable = UnallocatedSpaceTable;
            this.UnallocatedSpaceBitmap = UnallocatedSpaceBitmap;
            this.PartitionIntegrityTable = PartitionIntegrityTable;
            this.FreedSpaceTable = FreedSpaceTable;
            this.FreedSpaceBitmap = FreedSpaceBitmap;
        }

        public short_ad UnallocatedSpaceTable;
        public short_ad UnallocatedSpaceBitmap;
        public short_ad PartitionIntegrityTable;
        public short_ad FreedSpaceTable;
        public short_ad FreedSpaceBitmap;
        private readonly byte[] Reserved = new byte[88];

        public int Length { get { return 128; } }
        public static implicit operator byte[](PartitionHeaderDescriptor p)
        {
            byte[] output = new byte[128];
            Array.Copy(p.UnallocatedSpaceTable, 0, output, 0, 8);
            Array.Copy(p.UnallocatedSpaceBitmap, 0, output, 8, 8);
            Array.Copy(p.PartitionIntegrityTable, 0, output, 16, 8);
            Array.Copy(p.FreedSpaceTable, 0, output, 24, 8);
            Array.Copy(p.FreedSpaceBitmap, 0, output, 32, 8);
            Array.Copy(p.Reserved, 0, output, 40, 88);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.3.9, ECMA-167 4/14.13</summary>
    public class PartitionIntegrityEntry
    {
        public PartitionIntegrityEntry(tag DescriptorTag, icbtag ICBTag, timestamp RecordingTime,
            byte IntegrityType, EntityID ImplementationIdentifier, byte[] ImplementationUse)
        {
            this.DescriptorTag = DescriptorTag;
            this.ICBTag = ICBTag;
            this.RecordingTime = RecordingTime;
            this.IntegrityType = IntegrityType;
            this.ImplementationIdentifier = ImplementationIdentifier;
            this.ImplementationUse = ImplementationUse;
        }

        public tag DescriptorTag;
        public icbtag ICBTag;
        public timestamp RecordingTime;
        public byte IntegrityType;
        private readonly byte[] Reserved = new byte[175];
        public EntityID ImplementationIdentifier;
        private byte[] _ImplementationUse = new byte[256];
        public byte[] ImplementationUse
        {
            get { return _ImplementationUse; }
            set { if (value.Length != 256) throw new ArgumentException(); else _ImplementationUse = value; }
        }

        public int Length { get { return 512; } }
        public static implicit operator byte[](PartitionIntegrityEntry p)
        {
            byte[] output = new byte[512];
            Array.Copy(p.DescriptorTag, 0, output, 0, 16);
            Array.Copy(p.ICBTag, 0, output, 16, 20);
            Array.Copy(p.RecordingTime, 0, output, 36, 12);
            output[48] = p.IntegrityType;
            Array.Copy(p.Reserved, 0, output, 49, 175);
            Array.Copy(p.ImplementationIdentifier, 0, output, 224, 32);
            Array.Copy(p.ImplementationUse, 0, output, 256, 256);
            return output;
        }
    }
    /// <summary>Defined in ECMA-167 3/10.7.1</summary>
    public class PartitionMapHeader 
    {
        public PartitionMapHeader(byte PartitionMapType, byte PartitionMapLength)
        {
            this.PartitionMapType = PartitionMapType;
            this.PartitionMapLength = PartitionMapLength;
        }

        public byte PartitionMapType;
        public byte PartitionMapLength;

        public int Length { get { return 2; } }
        public static implicit operator byte[](PartitionMapHeader p)
        {
            byte[] output = new byte[2];
            output[0] = p.PartitionMapType;
            output[1] = p.PartitionMapLength;
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.3.12, ECMA-167 4/14.16.1</summary>
    public class PathComponent
    {
        public PathComponent(byte ComponentType, ushort ComponentFileVersionNumber, byte[] ComponentIdentifier)
        {
            this.ComponentType = ComponentType;
            this.ComponentFileVersionNumber = ComponentFileVersionNumber;
            this.ComponentIdentifier = ComponentIdentifier;
        }

        public byte ComponentType;
        public byte LengthofComponentIdentifier { get { return (byte)ComponentIdentifier.Length; } }
        public ushort ComponentFileVersionNumber;
        public byte[] ComponentIdentifier;

        public int Length { get { return 4 + LengthofComponentIdentifier; } }
        public static implicit operator byte[](PathComponent p)
        {
            byte[] output = new byte[p.Length];
            output[0] = p.ComponentType;
            output[1] = p.LengthofComponentIdentifier;
            Array.Copy(ByteFactory.Convert(p.ComponentFileVersionNumber), 0, output, 2, 2);
            Array.Copy(p.ComponentIdentifier, 0, output, 4, p.LengthofComponentIdentifier);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.2.2, ECMA-167 3/10.1</summary>
    public class PrimaryVolumeDescriptor
    {
        public PrimaryVolumeDescriptor(tag DescriptorTag, uint VolumeDescriptorSequenceNumber, uint PrimaryVolumeDescriptorNumber,
            byte[] VolumeIdentifier, ushort VolumeSequenceNumber, ushort MaximumVolumeSequenceNumber, ushort InterchangeLevel,
            ushort MaximumInterchangeLevel, uint CharacterSetList, uint MaximumCharacterSetList, byte[] VolumeSetIdentifier,
            charspec DescriptorCharacterSet, charspec ExplanatoryCharacterSet, extent_ad VolumeAbstract, extent_ad VolumeCopyrightNotice,
            EntityID ApplicationIdentifier, timestamp RecordingDateandTime, EntityID ImplementationIdentifier, byte[] ImplementationUse,
            uint PredecessorVolumeDescriptorSequenceLocation, ushort Flags)
        {
            this.DescriptorTag = DescriptorTag;
            this.VolumeDescriptorSequenceNumber = VolumeDescriptorSequenceNumber;
            this.PrimaryVolumeDescriptorNumber = PrimaryVolumeDescriptorNumber;
            this.VolumeIdentifier = VolumeIdentifier;
            this.VolumeSequenceNumber = VolumeSequenceNumber;
            this.MaximumVolumeSequenceNumber = MaximumVolumeSequenceNumber;
            this.InterchangeLevel = InterchangeLevel;
            this.MaximumInterchangeLevel = MaximumInterchangeLevel;
            this.CharacterSetList = CharacterSetList;
            this.MaximumCharacterSetList = MaximumCharacterSetList;
            this.VolumeSetIdentifier = VolumeSetIdentifier;
            this.DescriptorCharacterSet = DescriptorCharacterSet;
            this.ExplanatoryCharacterSet = ExplanatoryCharacterSet;
            this.VolumeAbstract = VolumeAbstract;
            this.VolumeCopyrightNotice = VolumeCopyrightNotice;
            this.ApplicationIdentifier = ApplicationIdentifier;
            this.RecordingDateandTime = RecordingDateandTime;
            this.ImplementationIdentifier = ImplementationIdentifier;
            this.ImplementationUse = ImplementationUse;
            this.PredecessorVolumeDescriptorSequenceLocation = PredecessorVolumeDescriptorSequenceLocation;
            this.Flags = Flags;
        }

        public tag DescriptorTag;
        public uint VolumeDescriptorSequenceNumber;
        public uint PrimaryVolumeDescriptorNumber;
        private byte[] _VolumeIdentifier;
        public byte[] VolumeIdentifier
        {
            get { return _VolumeIdentifier; }
            set { if (value.Length != 32) throw new ArgumentException(); else _VolumeIdentifier = value; }
        }
        public ushort VolumeSequenceNumber;
        public ushort MaximumVolumeSequenceNumber;
        public ushort InterchangeLevel;
        public ushort MaximumInterchangeLevel;
        public uint CharacterSetList;
        public uint MaximumCharacterSetList;
        private byte[] _VolumeSetIdentifier;
        public byte[] VolumeSetIdentifier
        {
            get { return _VolumeSetIdentifier; }
            set { if (value.Length != 128) throw new ArgumentException(); else _VolumeSetIdentifier = value; }
        }
        public charspec DescriptorCharacterSet;
        public charspec ExplanatoryCharacterSet;
        public extent_ad VolumeAbstract;
        public extent_ad VolumeCopyrightNotice;
        public EntityID ApplicationIdentifier;
        public timestamp RecordingDateandTime;
        public EntityID ImplementationIdentifier;
        private byte[] _ImplementationUse;
        public byte[] ImplementationUse
        {
            get { return _ImplementationUse; }
            set { if (value.Length != 64)throw new ArgumentException(); else _ImplementationUse = value; }
        }
        public uint PredecessorVolumeDescriptorSequenceLocation;
        public ushort Flags;
        private readonly byte[] Reserved = new byte[22]; // <summary>Defined in OSTA UDF 1.02 2.2.2

        public int Length { get { return 512; } }
        public static implicit operator byte[](PrimaryVolumeDescriptor p)
        {
            byte[] output = new byte[512];
            Array.Copy(p.DescriptorTag, 0, output, 0, 16);
            Array.Copy(ByteFactory.Convert(p.VolumeDescriptorSequenceNumber), 0, output, 16, 4);
            Array.Copy(ByteFactory.Convert(p.PrimaryVolumeDescriptorNumber), 0, output, 20, 4);
            Array.Copy(p.VolumeIdentifier, 0, output, 24, 32);
            Array.Copy(ByteFactory.Convert(p.VolumeSequenceNumber), 0, output, 56, 2);
            Array.Copy(ByteFactory.Convert(p.MaximumVolumeSequenceNumber), 0, output, 58, 2);
            Array.Copy(ByteFactory.Convert(p.InterchangeLevel), 0, output, 60, 2);
            Array.Copy(ByteFactory.Convert(p.MaximumInterchangeLevel), 0, output, 62, 2);
            Array.Copy(ByteFactory.Convert(p.CharacterSetList), 0, output, 64, 4);
            Array.Copy(ByteFactory.Convert(p.MaximumCharacterSetList), 0, output, 68, 4);
            Array.Copy(p.VolumeSetIdentifier, 0, output, 72, 128);
            Array.Copy(p.DescriptorCharacterSet, 0, output, 200, 64);
            Array.Copy(p.ExplanatoryCharacterSet, 0, output, 264, 64);
            Array.Copy(p.VolumeAbstract, 0, output, 328, 8);
            Array.Copy(p.VolumeCopyrightNotice, 0, output, 336, 8);
            Array.Copy(p.ApplicationIdentifier, 0, output, 344, 32);
            Array.Copy(p.RecordingDateandTime, 0, output, 376, 12);
            Array.Copy(p.ImplementationIdentifier, 0, output, 388, 32);
            Array.Copy(p.ImplementationUse, 0, output, 420, 64);
            Array.Copy(ByteFactory.Convert(p.PredecessorVolumeDescriptorSequenceLocation), 0, output, 484, 4);
            Array.Copy(ByteFactory.Convert(p.Flags), 0, output, 488, 2);
            Array.Copy(p.Reserved, 0, output, 490, 22);
            return output;
        }
    }
    /// <summary>Defined in ECMA-167 4/14.14.1</summary>
    public class short_ad
    {
        public short_ad(uint ExtentInterpretationAndLength, uint ExtentPosition)
        {
            this.ExtentInterpretationAndLength = ExtentInterpretationAndLength;
            this.ExtentPosition = ExtentPosition;
        }

        public short_ad(byte ExtentInterpretation, uint ExtentLength, uint ExtentPosition)
        {
            this.ExtentInterpretationAndLength = (uint)ExtentInterpretation << 30 | (0x3FFF & ExtentLength);
            this.ExtentPosition = ExtentPosition;
        }

        public uint ExtentInterpretationAndLength;
        public uint ExtentPosition;

        public byte ExtentInterpretation
        {
            get { return (byte)(ExtentInterpretationAndLength >> 30); }
            set { ExtentInterpretationAndLength = (uint)ExtentInterpretation << 30 | (0x3FFF & ExtentInterpretationAndLength); }
        }

        public uint ExtentLength
        {
            get { return 0x3FFF & ExtentInterpretationAndLength; }
            set { ExtentInterpretationAndLength = (0x3FFF & ExtentLength) | (0xC000 & ExtentInterpretationAndLength); }
        }

        public int Length { get { return 8; } }
        public static implicit operator byte[](short_ad s)
        {
            byte[] output = new byte[8];
            Array.Copy(ByteFactory.Convert(s.ExtentLength), 0, output, 0, 4);
            Array.Copy(ByteFactory.Convert(s.ExtentPosition), 0, output, 4, 4);
            return output;
        }
        public static implicit operator short_ad(byte[] b)
        {
            Array.Reverse(b, 0, 4);
            Array.Reverse(b, 4, 4);
            return new short_ad(BitConverter.ToUInt32(b, 0), BitConverter.ToUInt32(b, 4));
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.3.8, ECMA-167 4/14.12</summary>
    public class SpaceBitmap
    {
        // Sanity checking effectively makes the size of Bitmap, in bytes, immutable. The data within Bitmap can of
        // course be changed, and NumberOfBits can change (slightly) as well, as long as it agrees with NumberOfBytes.

        public SpaceBitmap(tag DescriptorTag, uint NumberOfBits, byte[] Bitmap)
        {
            this.DescriptorTag = DescriptorTag;
            _Bitmap = Bitmap; // Bypass size check
            this.NumberOfBits = NumberOfBits;
        }

        public tag DescriptorTag;
        private uint _NumberOfBits;
        public uint NumberOfBits
        {
            get { return _NumberOfBits; }
            set { if (Decimal.Ceiling(value / 8) != NumberOfBytes) throw new ArgumentException(); else _NumberOfBits = value; }
        }
        public uint NumberOfBytes { get { return (uint)Bitmap.Length; } }
        private byte[] _Bitmap;
        public byte[] Bitmap
        {
            get { return _Bitmap; }
            set { if (value.Length != _Bitmap.Length) throw new ArgumentException(); else _Bitmap = value; }
        }

        public int Length { get { return 24 + (int)NumberOfBytes; } }
        public static implicit operator byte[](SpaceBitmap s)
        {
            byte[] output = new byte[s.Length];
            Array.Copy(s.DescriptorTag, 0, output, 0, 16);
            Array.Copy(ByteFactory.Convert(s.NumberOfBits), 0, output, 16, 4);
            Array.Copy(ByteFactory.Convert(s.NumberOfBytes), 0, output, 20, 4);
            Array.Copy(s.Bitmap, 0, output, 42, (int)s.NumberOfBytes);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.2.1, 2.3.1, ECMA-167 3/7.2</summary>
    public class tag
    {
        public tag(tagIdent TagIdentifier, ushort DescriptorVersion, ushort TagSerialNumber,
            ushort DescriptorCRC, ushort DescriptorCRCLength, uint TagLocation)
        {
            this.TagIdentifier = TagIdentifier;
            this.DescriptorVersion = DescriptorVersion;
            this.TagSerialNumber = TagSerialNumber;
            this.DescriptorCRC = DescriptorCRC;
            this.DescriptorCRCLength = DescriptorCRCLength;
            this.TagLocation = TagLocation;
        }

        public tagIdent TagIdentifier;
        public ushort DescriptorVersion;
        byte TagChecksum
        {
            get
            {
                byte[] tag = this;
                ushort sum = 0;
                for (int i = 0; i < 16; i++)
                    if (i != 4)
                        sum += tag[i];
                return (byte)(sum % 256); // TODO: Will "return (byte)sum;" do the same thing, or throw exceptions?
            }
        }
        private readonly byte Reserved = 0x00;
        public ushort TagSerialNumber;
        public ushort DescriptorCRC;
        public ushort DescriptorCRCLength;
        public uint TagLocation;

        public int Length { get { return 16; } }
        public static implicit operator byte[](tag t)
        {
            byte[] output = new byte[16];
            Array.Copy(ByteFactory.Convert(t.TagIdentifier), 0, output, 0, 2);
            Array.Copy(ByteFactory.Convert(t.DescriptorVersion), 0, output, 2, 2);
            output[4] = t.TagChecksum;
            output[5] = t.Reserved;
            Array.Copy(ByteFactory.Convert(t.TagSerialNumber), 0, output, 6, 2);
            Array.Copy(ByteFactory.Convert(t.DescriptorCRC), 0, output, 8, 2);
            Array.Copy(ByteFactory.Convert(t.DescriptorCRCLength), 0, output, 10, 2);
            Array.Copy(ByteFactory.Convert(t.TagLocation), 0, output, 12, 4);
            return output;
        }
    }
    /// <summary>Defined in ECMA-167 3/7.2.1, ECMA-167 4/7.2.1, ISO 13346 3/7.2.1, ISO 13346 4/7.2.1</summary>
    public class tagIdent
    {
        private ushort value;
        public tagIdent(ushort u)
        { value = u; }

        public static tagIdent PrimaryVolumeDescriptor()
        { return 1; }
        public static tagIdent AnchorVolumeDescriptorPointer()
        { return 2; }
        public static tagIdent VolumeDescriptorPointer()
        { return 3; }
        public static tagIdent ImplementationUseVolumeDescriptor()
        { return 4; }
        public static tagIdent PartitionDescriptor()
        { return 5; }
        public static tagIdent LogicalVolumeDescriptor()
        { return 6; }
        public static tagIdent UnallocatedSpaceDescriptor()
        { return 7; }
        public static tagIdent TerminatingDescriptor()
        { return 8; }
        public static tagIdent LogicalVolumeIntegrityDescriptor()
        { return 9; }
        public static tagIdent FileSetDescriptor()
        { return 256; }
        public static tagIdent FileIdentifierDescriptor()
        { return 257; }
        public static tagIdent AllocationExtentDescriptor()
        { return 258; }
        public static tagIdent IndirectEntry()
        { return 259; }
        public static tagIdent TerminalEntry()
        { return 260; }
        public static tagIdent FileEntry()
        { return 261; }
        public static tagIdent ExtendedAttributeHeaderDescriptor()
        { return 262; }
        public static tagIdent UnallocatedSpaceEntry()
        { return 263; }
        public static tagIdent SpaceBitmapDescriptor()
        { return 264; }
        public static tagIdent PartitionIntegrityEntry()
        { return 265; }
        public static tagIdent ExtendedFileEntry()
        { return 266; }

        public static implicit operator tagIdent(ushort u)
        { return new tagIdent(u); }
        public static implicit operator ushort(tagIdent t)
        { return t.value; }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.1.4, ECMA-167 1/7.3</summary>
    public class timestamp
    {
        // TODO: Subsecond values are ignored when casting to/from DateTime. According to the specs this is legal, but it's sloppy and should be fixed.
        public timestamp(ushort TypeAndTimezone, ushort Year, byte Month, byte Day, byte Hour, byte Minute,
            byte Second, byte Centiseconds, byte HundredsofMicroseconds, byte Microseconds)
        {
            this.TypeAndTimezone = TypeAndTimezone;
            this.Year = Year;
            this.Month = Month;
            this.Day = Day;
            this.Hour = Hour;
            this.Minute = Minute;
            this.Second = Second;
            this.Centiseconds = Centiseconds;
            this.HundredsofMicroseconds = HundredsofMicroseconds;
            this.Microseconds = Microseconds;
        }

        public timestamp(byte Type, ushort Timezone, ushort Year, byte Month, byte Day, byte Hour,
            byte Minute, byte Second, byte Centiseconds, byte HundredsofMicroseconds, byte Microseconds)
        {
            this.TypeAndTimezone = (ushort)(((ushort)Type << 12) | (Timezone & 0x0FFF));
            this.Year = Year;
            this.Month = Month;
            this.Day = Day;
            this.Hour = Hour;
            this.Minute = Minute;
            this.Second = Second;
            this.Centiseconds = Centiseconds;
            this.HundredsofMicroseconds = HundredsofMicroseconds;
            this.Microseconds = Microseconds;
        }

        public ushort TypeAndTimezone;
        public ushort Year;
        public byte Month;
        public byte Day;
        public byte Hour;
        public byte Minute;
        public byte Second;
        public byte Centiseconds;
        public byte HundredsofMicroseconds;
        public byte Microseconds;

        public byte Type
        {
            get { return (byte)((TypeAndTimezone & 0xF000) >> 12); }
            set { TypeAndTimezone = (ushort)((TypeAndTimezone & 0x0FFF) | ((ushort)value << 12)); }
        }

        public ushort Timezone
        {
            get { return (ushort)(TypeAndTimezone & 0x0FFF); }
            set { TypeAndTimezone = (ushort)((TypeAndTimezone & 0xF000) | (value & 0x0FFF)); }
        }

        public static implicit operator DateTime(timestamp t)
        {
            DateTime output = new DateTime(t.Year, t.Month, t.Day, t.Hour, t.Minute, t.Second, DateTimeKind.Utc);
            if (t.Timezone != 0x0801) // Not UTC. OSTA UDF 1.02 2.1.4.1 
                output.AddMinutes(t.Timezone);
            return output;
        }

        public static implicit operator timestamp(DateTime d)
        {
            if (d.Kind == DateTimeKind.Local)
                d = d.ToUniversalTime();
            return new timestamp(0x1801, (ushort)d.Year, (byte)d.Month, (byte)d.Day, (byte)d.Hour, (byte)d.Minute, (byte)d.Second, 0x00, 0x00, 0x00);
        }

        public int Length { get { return 12; } }
        public static implicit operator byte[](timestamp t)
        {
            byte[] output = new byte[12];
            Array.Copy(ByteFactory.Convert(t.TypeAndTimezone), 0, output, 0, 2);
            Array.Copy(ByteFactory.Convert(t.Year), 0, output, 2, 2);
            output[4] = t.Month;
            output[5] = t.Day;
            output[6] = t.Hour;
            output[7] = t.Minute;
            output[8] = t.Second;
            output[9] = t.Centiseconds;
            output[10] = t.HundredsofMicroseconds;
            output[11] = t.Microseconds;
            return output;
        }
    }
    /// <summary>Defined in ECMA-167 3/10.7.2</summary>
    public class Type1PartitionMap 
    {
        public Type1PartitionMap(ushort VolumeSequenceNumber, ushort PartitionNumber)
        {
            this.VolumeSequenceNumber = VolumeSequenceNumber;
            this.PartitionNumber = PartitionNumber;
        }

        public readonly PartitionMapHeader Header = new PartitionMapHeader(0x01, 0x08); // <summary>Defined in ECMA-167 10.7.2
        public readonly byte PartitionMapType = 0x01; // <summary>Defined in ECMA-167 10.7.2.1
        public readonly byte PartitionMapLength = 0x06; // <summary>Defined in ECMA-167 10.7.2.2
        public ushort VolumeSequenceNumber;
        public ushort PartitionNumber;

        public int Length { get { return 8; } }
        public static implicit operator byte[](Type1PartitionMap t)
        {
            byte[] output = new byte[8];
            Array.Copy(t.Header, 0, output, 0, 2);
            output[2] = t.PartitionMapType;
            output[3] = t.PartitionMapLength;
            Array.Copy(ByteFactory.Convert(t.VolumeSequenceNumber), 0, output, 4, 2);
            Array.Copy(ByteFactory.Convert(t.PartitionNumber), 0, output, 6, 2);
            return output;
        }
    }
    /// <summary>Defined in ECMA-167 3/10.7.3</summary>
    public class Type2PartitionMap 
    {
        public Type2PartitionMap(byte[] PartitionIdentifier)
        {
            this.PartitionIdentifier = PartitionIdentifier;
        }

        public readonly PartitionMapHeader Header = new PartitionMapHeader(0x02, 0x40); // <summary>Defined in ECMA-167 10.7.3
        private byte[] _PartitionIdentifier;
        public byte[] PartitionIdentifier
        {
            get { return _PartitionIdentifier; }
            set { if (value.Length != 62) throw new ArgumentException(); else _PartitionIdentifier = value; }
        }

        public int Length { get { return 64; } }
        public static implicit operator byte[](Type2PartitionMap t)
        {
            byte[] output = new byte[64];
            Array.Copy(t.Header, 0, output, 0, 2);
            Array.Copy(t.PartitionIdentifier, 0, output, 2, 62);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.2.5, ECMA-167 3/10.8</summary>
    public class UnallocatedSpaceDescriptor
    {
        public UnallocatedSpaceDescriptor(tag DescriptorTag, uint VolumeDescriptorSequenceNumber, List<extent_ad> AllocationDescriptors)
        {
            this.DescriptorTag = DescriptorTag;
            this.VolumeDescriptorSequenceNumber = VolumeDescriptorSequenceNumber;
            this.AllocationDescriptors = AllocationDescriptors;
        }

        public tag DescriptorTag;
        public uint VolumeDescriptorSequenceNumber;
        public uint NumberofAllocationDescriptors { get { return (uint)AllocationDescriptors.Count; } }
        public List<extent_ad> AllocationDescriptors;

        public int Length { get { return 24 + (8 * (int)NumberofAllocationDescriptors); } }
        public static implicit operator byte[](UnallocatedSpaceDescriptor u)
        {
            byte[] output = new byte[u.Length];
            Array.Copy(u.DescriptorTag, 0, output, 0, 16);
            Array.Copy(ByteFactory.Convert(u.VolumeDescriptorSequenceNumber), 0, output, 16, 4);
            Array.Copy(ByteFactory.Convert(u.NumberofAllocationDescriptors), 0, output, 20, 4);
            for (int i = 0; i < u.AllocationDescriptors.Count; i++)
                Array.Copy(u.AllocationDescriptors[i], 0, output, 24 + (i * 8), 8);
            return output;
        }
    }
    /// <summary>Defined in OSTA UDF 1.02 2.3.7, ECMA-167 4/14.11</summary>
    public class UnallocatedSpaceEntry 
    {
        public UnallocatedSpaceEntry(tag DescriptorTag, icbtag ICBTag, List<short_ad> AllocationDescriptors)
        {
            this.DescriptorTag = DescriptorTag;
            this.ICBTag = ICBTag;
            this.AllocationDescriptors = AllocationDescriptors;
        }

        public tag DescriptorTag;
        public icbtag ICBTag;
        public uint LengthofAllocationDescriptors { get { return (uint)(AllocationDescriptors.Count * 8); } }
        public List<short_ad> AllocationDescriptors;

        public int Length { get { return 40 + (int)LengthofAllocationDescriptors; } }
        public static implicit operator byte[](UnallocatedSpaceEntry u)
        {
            byte[] output = new byte[u.Length];
            Array.Copy(u.DescriptorTag, 0, output, 0, 16);
            Array.Copy(u.ICBTag, 0, output, 16, 20);
            Array.Copy(ByteFactory.Convert(u.LengthofAllocationDescriptors), 0, output, 36, 4);
            for (int i = 0; i < u.AllocationDescriptors.Count; i++)
                Array.Copy(u.AllocationDescriptors[i], 0, output, 40 + (8 * i), 8);
            return output;
        }
    }
    /// <summary>Defined in ISO 13346 2/9.1</summary>
    public class VolumeStructureDescriptor
    {
        // TODO: Implement BOOT2 descriptor.

        public VolumeStructureDescriptor(byte StructureType, string StandardIdentifier,
            byte StructureVersion, byte[] StructureData)
        {
            this.StructureType = StructureType;
            this.StandardIdentifier = StandardIdentifier;
            this.StructureVersion = StructureVersion;
            this.StructureData = StructureData;
        }

        public byte StructureType;
        private string _StandardIdentifier;
        public string StandardIdentifier
        {
            get { return _StandardIdentifier; }
            set { if (value.Length != 5) throw new ArgumentException(); _StandardIdentifier = value; }
        }
        public byte StructureVersion;
        private byte[] _StructureData;
        public byte[] StructureData
        { 
            get { return _StructureData; }
            set { if (value.Length != 2041) throw new ArgumentException(); _StructureData = value; }
        }

        public int Length { get { return 2048; } }
        public static implicit operator byte[](VolumeStructureDescriptor v)
        {
            byte[] output = new byte[2048];
            output[0] = v.StructureType;
            Array.Copy(ByteFactory.Convert(v.StandardIdentifier), 0, output, 1, 5);
            output[6] = v.StructureVersion;
            Array.Copy(v.StructureData, 0, output, 7, 2041);
            return output;
        }

        /// <summary>
        /// Defined by ISO 13346 2/9.2
        /// </summary>
        public static byte[] BeginningExtendedAreaDescriptor()
        { return new VolumeStructureDescriptor(0x00, "BEA01", 0x01, new byte[2041]); }

        /// <summary>
        /// Defined by ISO 13346:1995 3/9.1
        /// </summary>
        public static byte[] NonSequentialRecording2Descriptor()
        { return new VolumeStructureDescriptor(0x00, "NSR02", 0x01, new byte[2041]); }

        /// <summary>
        /// Defined by ISO 13346:1999 3/9.1
        /// </summary>
        public static byte[] NonSequentialRecording3Descriptor()
        { return new VolumeStructureDescriptor(0x00, "NSR03", 0x01, new byte[2041]); }

        /// <summary>
        /// Defined by ISO 13346 2/9.3
        /// </summary>
        public static byte[] TerminatingExtendedAreaDescriptor()
        { return new VolumeStructureDescriptor(0x00, "TEA01", 0x01, new byte[2041]); }
    }
}












